﻿using HelixToolkit.Wpf;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;

namespace QuickStart_Project_01.Model3Ds
{
    public class ParticleModel3D : RenderingModelVisual3D
    {
        GeometryModel3D model = new GeometryModel3D();
        MeshGeometry3D mesh = new MeshGeometry3D();

        // 粒子纹理材质（支持渐变不透明度）
        public Brush Texture
        {
            get { return (Brush)GetValue(TextureProperty); }
            set { SetValue(TextureProperty, value); }
        }

        public static readonly DependencyProperty TextureProperty =
            DependencyProperty.Register("Texture", typeof(Brush), typeof(ParticleModel3D),
                new PropertyMetadata(null, new PropertyChangedCallback((d, p) =>
                {
                    (d as ParticleModel3D).TextureChanged();
                })));


        public ParticleModel3D()
        {
            model.Geometry = mesh;
            this.Content = model;
        }

        class Particle
        {
            internal Point3D Position;
            internal Vector3D Velocity; // speed
            internal double Rotation;
            internal double Size;
            internal double Age;
        }

        public Point3D Position
        {
            get { return (Point3D)GetValue(PositionProperty); }
            set { SetValue(PositionProperty, value); }
        }

        public static readonly DependencyProperty PositionProperty =
            DependencyProperty.Register("Position", typeof(Point3D), typeof(ParticleModel3D), new PropertyMetadata(new Point3D()));

        // 一些粒子系统的运行状态
        Stopwatch stopwatch = Stopwatch.StartNew();
        Random random = new Random();
        double previousTime = double.NaN;
        double particlesToEmit = 0.0;
        double DegToRad = Math.PI / 180;

        // 粒子初始方向、半径、速度、尺寸等参数
        Vector3D StartDirection = new Vector3D(0, 0, 1);
        double startRadius = 1.0;
        double startVelocity = 2.0;
        double startVelocityRandomness = 1;
        double startSize = 0.5d;

        ProjectionCamera camera;
        List<Particle> particles = new List<Particle>();

        // 当纹理发生变化时，生成10个透明度不同的图层，作为粒子的 Alpha 混合使用
        private void TextureChanged()
        {
            int w = 256, h = 256;
            var bitmap = new RenderTargetBitmap(10 * w, h, 96, 96, PixelFormats.Pbgra32);

            for (int i = 0; i < 10; i++)
            {
                var rect = new Rectangle()
                {
                    Opacity = 1 - (i * 1.0 / 10),
                    Fill = this.Texture,
                    Width = w,
                    Height = h
                };
                rect.Arrange(new Rect(w * i, 0, w, h));
                bitmap.Render(rect);
            }

            // 将渲染结果作为贴图材质
            var brush = new ImageBrush(bitmap) { ViewportUnits = BrushMappingMode.Absolute };
            brush.Freeze();
            var meterial = new DiffuseMaterial(brush) { AmbientColor = Colors.White };
            meterial.Freeze();
            this.model.Material = meterial;
        }

        // 根据给定的方向 z 和扩散角度 speading，生成一个在该方向附近的随机单位向量
        private Vector3D CreateRandomVector(Vector3D z, double speading)
        {
            // 将扩散角度 speading（单位为度）转为弧度，并乘以 [0,1) 的随机因子，得到倾斜角 theta（与 z 的夹角）
            var theta = speading * DegToRad * random.NextDouble();

            // 生成一个 [0, 2π) 之间的随机角度 phi，用于绕 z 轴旋转
            var phi = 2 * Math.PI * random.NextDouble();

            // 找到一个垂直于 z 向量的向量 x
            var x = z.FindAnyPerpendicular();

            // 使用叉积计算另一个与 z 和 x 垂直的向量 y，使 x、y、z 构成一个正交坐标系
            var y = Vector3D.CrossProduct(z, x);

            // 计算球坐标中的方向向量的分量，theta 控制从 z 偏离的角度，phi 控制绕 z 的旋转
            var sintheta = Math.Sin(theta);
            var costheta = Math.Cos(theta);
            var sinphi = Math.Sin(phi);
            var cosphi = Math.Cos(phi);

            // 将球坐标系下的向量转换为笛卡尔坐标系：r * (x * sinθ * cosφ + y * sinθ * sinφ + z * cosθ)
            // 这里 r = 1，因此返回一个方向在 z 附近的单位向量
            return (x * sintheta * cosphi) + (y * sintheta * sinphi) + (z * costheta);
        }


        // 生成一个粒子
        private void EmitOne()
        {
            // 创建一个随机的起始方向向量，基于StartDirection并允许40度的随机偏移
            var startDirection = CreateRandomVector(StartDirection, 40d);

            // 获取当前发射器的位置作为粒子的初始位置
            var postion = this.Position;

            // 获取标准化的起始方向向量z
            var z = this.StartDirection;
            z.Normalize(); // 将z向量归一化(长度为1)

            // 找到与z垂直的任意一个向量x
            var x = z.FindAnyPerpendicular();

            // 通过叉积计算第三个垂直向量y，形成正交坐标系
            var y = Vector3D.CrossProduct(z, x);

            // 如果起始半径大于0，在垂直于起始方向的平面上随机偏移位置
            if (startRadius > 0)
            {
                // 生成一个0-2π的随机角度psi
                var psi = 2 * Math.PI * random.NextDouble();

                // 在x-y平面(垂直于起始方向的平面)上计算偏移量并加到位置上
                postion += this.startRadius * ((x * Math.Cos(psi)) + (y * Math.Sin(psi)));
            }

            // 计算粒子的速度，基础速度加上随机分量(0到0.5倍随机性系数的随机值)
            var speed = startVelocity + (this.startVelocityRandomness * (random.NextDouble() * 0.5));

            // 创建新粒子对象并初始化其属性
            var particle = new Particle()
            {
                Position = postion,       // 设置位置(可能包含随机偏移)
                Size = startSize,          // 设置初始大小
                Age = 0,                  // 年龄初始化为0
                Rotation = 0,             // 旋转角度初始化为0
                Velocity = speed * startDirection // 设置速度(大小和方向)
            };

            // 在粒子数组中查找第一个空位(null)
            int index = this.particles.FindIndex(p => p == null);

            // 如果找到空位，将新粒子放入该位置
            if (index >= 0)
                particles[index] = particle;
            // 如果没有空位，将新粒子添加到数组末尾
            else
                particles.Add(particle);
        }

        // 调整集合长度，避免内存重复申请
        private void AdjustListLenght<T>(IList<T> list, int targetLenght)
        {
            int n = list.Count;
            for (int i = n - 1; i >= targetLenght; i--) list.RemoveAt(i);
            for (int i = 0; i < targetLenght - n; i++) list.Add(default(T));
        }

        // 更新粒子系统的状态并刷新 Mesh
        private void Update(double time)
        {
            if (double.IsNaN(previousTime))
            {
                this.previousTime = time;
                return;
            }

            var deltaTime = time - previousTime;
            this.previousTime = time;

            // 粒子发射速率：每秒发射 40 个
            particlesToEmit += deltaTime * 40;

            while (particlesToEmit > 1)
            {
                EmitOne();
                particlesToEmit--;
            }

            // 粒子动力学参数
            var angularVelocity = 20 * DegToRad;
            var veloctiryDaming = 1.0;
            var accelerationDirection = new Vector3D(3, 0, 1);
            accelerationDirection.Normalize();
            var acceleration = 4.0;
            var accelerationSpreading = 10.0;
            var sizeRate = 2.0;
            var fadeOutTime = 0.5;
            var lifeTime = 20.0;

            // 更新粒子状态
            for (int i = 0; i < particles.Count; i++)
            {
                var p = particles[i];
                p.Age += deltaTime;

                if (p.Age > lifeTime)
                {
                    particles.RemoveAt(i);
                    i--;
                    continue;
                }

                var a = accelerationSpreading > 0 ?
                        CreateRandomVector(accelerationDirection, accelerationSpreading) :
                        accelerationDirection;

                p.Position += p.Velocity * deltaTime;
                p.Size += sizeRate * deltaTime;
                p.Velocity = (p.Velocity * veloctiryDaming) + (a * acceleration * deltaTime);
            }

            // 渲染到 mesh 中
            var count = particles.Count;
            var positions = mesh.Positions ?? new Point3DCollection(count * 4);
            var triangleIndices = mesh.TriangleIndices ?? new Int32Collection(count * 6);
            var textureCoordinates = mesh.TextureCoordinates ?? new PointCollection(count * 4);

            if (positions.Count != count * 4)
            {
                int previousAliveParticle = positions.Count / 4;

                AdjustListLenght(positions, count * 4);
                AdjustListLenght(triangleIndices, count * 6);
                AdjustListLenght(textureCoordinates, count * 4);

                for (int i = previousAliveParticle; i < count; i++)
                {
                    int i4 = i * 4, i6 = i * 6;
                    triangleIndices[i6] = i4;
                    triangleIndices[i6 + 1] = i4 + 1;
                    triangleIndices[i6 + 2] = i4 + 2;
                    triangleIndices[i6 + 3] = i4 + 3;
                    triangleIndices[i6 + 4] = i4 + 4;
                    triangleIndices[i6 + 5] = i4;
                }
            }

            // 获取摄像机信息，用于构建朝向摄像机的粒子面片（billboarding）
            if (this.camera == null)
            {
                var viewport = this.GetViewport3D();
                this.camera = (ProjectionCamera)viewport.Camera;
            }

            var cameraPosition = this.camera.Position;
            var upVector = this.camera.UpDirection;
            var z = this.camera.LookDirection;
            var x = Vector3D.CrossProduct(z, upVector);
            var y = Vector3D.CrossProduct(x, z);
            x.Normalize(); y.Normalize();

            // 对粒子按摄像机距离排序，确保 alpha blending 视觉正确
            var sort_particale = particles.OrderBy(p =>
                -Vector3D.DotProduct(p.Position - cameraPosition, this.camera.LookDirection));

            int j = 0;
            foreach (var p in sort_particale)
            {
                var half_size = p.Size / 2;
                var j4 = j * 4;
                j++;

                // 粒子旋转（2D空间）
                var cos = Math.Cos(p.Rotation);
                var sin = Math.Sin(p.Rotation);

                var p0 = new Point(half_size * (cos + sin), half_size * (sin - cos));
                var p1 = new Point(half_size * (cos - sin), half_size * (sin + cos));
                var p2 = new Point(-half_size * (cos + sin), half_size * (cos - sin));
                var p3 = new Point(half_size * (sin - cos), -half_size * (cos + sin));
                var pos = p.Position;

                // 设置每个粒子面片的四个顶点坐标
                positions[j4] = pos + (x * p0.X) + (y * p0.Y);
                positions[j4 + 1] = pos + (x * p1.X) + (y * p1.Y);
                positions[j4 + 2] = pos + (x * p2.X) + (y * p2.Y);
                positions[j4 + 3] = pos + (x * p3.X) + (y * p3.Y);

                // 根据生命周期设置透明度纹理坐标区间（用于 Alpha 过渡）
                var opacity = 1d;
                if (fadeOutTime < 1 && p.Age > lifeTime * fadeOutTime)
                {
                    opacity = 1 - (((p.Age / lifeTime) - fadeOutTime) / (1 - fadeOutTime));
                }

                int transparency = (int)((1 - opacity) * 10);
                var u0 = transparency / 10.0;
                var u1 = (transparency + 1.0) / 10.0;

                textureCoordinates[j4] = new Point(u1, 1);
                textureCoordinates[j4 + 1] = new Point(u1, 0);
                textureCoordinates[j4 + 2] = new Point(u0, 0);
                textureCoordinates[j4 + 3] = new Point(u0, 1);
            }

            this.mesh.Positions = positions;
            this.mesh.TriangleIndices = triangleIndices;
            this.mesh.TextureCoordinates = textureCoordinates;
        }

        // 每帧触发（CompositionTarget.Rendering），用于实时更新粒子
        protected override void OnCompositionTargetRendering(object sender, RenderingEventArgs eventArgs)
        {
            Update(stopwatch.ElapsedMilliseconds * 0.001);
        }

        // 当视觉树父级发生变动时，决定是否订阅渲染帧事件
        protected override void OnVisualParentChanged(DependencyObject oldParent)
        {
            base.OnVisualParentChanged(oldParent);

            if (oldParent == null)
                this.SubscribeToRenderingEvent();
            else
                this.UnsubscribeRenderingEvent();
        }
    }

}
